package weiyao.xinxidasai.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import weiyao.xinxidasai.Utils.Encryption.Chaos.CEOEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.ChaosEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.tokenCEOEncryptor;
import weiyao.xinxidasai.Utils.Encryption.Chaos.tokenChaosEncryptor;
import weiyao.xinxidasai.Utils.ThreadLocal.UserHolder;
import weiyao.xinxidasai.Utils.OSS.OSSUploader;
import weiyao.xinxidasai.mapper.FilesMapper;
import weiyao.xinxidasai.mapper.FolderMapper;
import weiyao.xinxidasai.mapper.UserMapper;
import weiyao.xinxidasai.mapper.VersionMapper;
import weiyao.xinxidasai.pojo.FFiles;
import weiyao.xinxidasai.pojo.FilesFolder;
import weiyao.xinxidasai.pojo.Users;
import weiyao.xinxidasai.pojo.Version;
import weiyao.xinxidasai.service.UploadService;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Service
public class UploadServiceImpl implements UploadService {
    private final ExecutorService executorService = Executors.newFixedThreadPool(4);
    private static final Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class);

    @Autowired
    private FilesMapper fileMapper;
    @Autowired
    private FolderMapper folderMapper;
    @Autowired
    private VersionMapper versionMapper;
    @Autowired
    private OSSUploader ossUploader;
    @Autowired
    private UserMapper userMapper;
    private final Map<Long, Long> uploadCompletionCache = new ConcurrentHashMap<>();

    // 添加缓存清理方法（防止内存泄漏）
    @Scheduled(fixedRate = 10 * 60 * 1000) // 每10分钟清理一次
    public void cleanupUploadCache() {
        long now = System.currentTimeMillis();
        uploadCompletionCache.entrySet().removeIf(entry ->
                (now - entry.getValue()) > TimeUnit.HOURS.toMillis(1) // 保留1小时
        );
    }

    // 普通文件上传处理
    @Override
    @Transactional
    public void processAndSaveFile(MultipartFile file, Long folderId, Long versionId, String encryptionType) throws Exception {
        Long userId = UserHolder.getUser().getUserId();
        FilesFolder folder = folderMapper.selectFolderById(folderId);
        Version version = versionMapper.getVersionByVersionId(versionId);

        // 创建文件记录（不包含OSS路径）
        FFiles fileRecord = createFileRecord(file, folder, version, userId, null, encryptionType);
        fileMapper.insertexcludeossNo(fileRecord);
        logger.info("创建文件记录: {} | 后缀: {} | 加密类型: {}",
                fileRecord.getDisplayName(), fileRecord.getFileType(), encryptionType);

        // 异步处理：保存文件 -> 加密 -> 生成OSS路径 -> 上传OSS
        executorService.submit(() -> {
            try {
                // 1. 将MultipartFile保存为临时文件
                File tempFile = saveMultipartFileToTemp(file);

                // 2. 根据Token和加密类型选择加密方式
                File encryptedFile = encryptFile(
                        tempFile,
                        version,
                        fileRecord.getFileType(),
                        fileRecord.getFilesToken(),
                        encryptionType
                );

                // 3. 生成OSS路径（在加密完成后）
                String ossPath = generateOssPath(userId, folder, fileRecord.getFileType());

                // 4. 上传到OSS
                uploadToOSS(encryptedFile, ossPath);

                // 5. 更新OSS路径到数据库
                fileMapper.updateOssNo(fileRecord.getFilesId(), ossPath);

                // 6. 清理临时文件
                deleteTempFiles(tempFile, encryptedFile);

                logger.info("文件上传成功: {} | 加密方式: {}{} | OSS路径: {}",
                        fileRecord.getDisplayName(),
                        fileRecord.getFilesToken() != null ? "Token+" : "",
                        encryptionType,
                        ossPath);
            } catch (Exception e) {
                handleUploadFailure(fileRecord.getFilesId(), e);
            }
        });
    }

    // 分片上传后的文件处理
    @Transactional
    @Override
    public void processAndSaveFileFromDisk(String filePath, String fileName,
                                           Long folderId, Long versionId, String md5, String encryptionType) {
        Long userId = UserHolder.getUser().getUserId();
        FilesFolder folder = folderMapper.selectById(folderId);
        if (folder == null) throw new RuntimeException("文件夹不存在");
        Version version = versionMapper.getVersionByVersionId(versionId);
        if (version == null) throw new RuntimeException("版本不存在");

        File originalFile = new File(filePath);

        // 创建文件记录（不包含OSS路径）
        FFiles fileRecord = createFileRecord(
                fileName,
                originalFile.length(),
                determineMimeType(fileName),
                folder,
                version,
                userId,
                md5,
                encryptionType
        );
        fileMapper.insertexcludeossNo(fileRecord);
        logger.info("创建分片文件记录: {} | 后缀: {} | 加密类型: {}",
                fileRecord.getDisplayName(), fileRecord.getFileType(), encryptionType);

        // 异步处理：加密 -> 生成OSS路径 -> 上传OSS
        executorService.submit(() -> {
            try {
                // 1. 根据Token和加密类型选择加密方式
                File encryptedFile = encryptFile(
                        originalFile,
                        version,
                        fileRecord.getFileType(),
                        fileRecord.getFilesToken(),
                        encryptionType
                );

                // 2. 生成OSS路径（在加密完成后）
                String ossPath = generateOssPath(userId, folder, fileRecord.getFileType());

                // 3. 上传到OSS
                uploadToOSS(encryptedFile, ossPath);

                // 4. 更新OSS路径到数据库
                fileMapper.updateOssNo(fileRecord.getFilesId(), ossPath);

                // 5. 清理临时文件
                deleteTempFiles(originalFile, encryptedFile);

                logger.info("分片文件上传成功: {} | 加密方式: {}{} | OSS路径: {}",
                        fileRecord.getDisplayName(),
                        fileRecord.getFilesToken() != null ? "Token+" : "",
                        encryptionType,
                        ossPath);
            } catch (Exception e) {
                handleUploadFailure(fileRecord.getFilesId(), e);
            }
        });
    }

    // ============== 核心加密方法 ==============

    /**
     * 统一的文件加密方法
     *
     * @param inputFile      输入文件
     * @param version        版本信息
     * @param fileExtension  文件扩展名
     * @param secretKey      Token密钥（可为空）
     * @param encryptionType 加密类型（standard/enhanced）
     * @return 加密后的文件
     */
    private File encryptFile(File inputFile, Version version, String fileExtension,
                             String secretKey, String encryptionType) throws Exception {
        // 创建加密临时文件（保留原始扩展名）
        String suffix = fileExtension.isEmpty() ? "" : "." + fileExtension;
        String prefix = "encrypted-" + (secretKey != null ? "token-" : "") + encryptionType + "-";
        File encryptedFile = File.createTempFile(prefix, suffix);

        try (InputStream inputStream = new FileInputStream(inputFile);
             OutputStream outputStream = new FileOutputStream(encryptedFile)) {

            // 根据加密类型和Token选择加密器
            if (secretKey != null) {
                if ("enhanced".equals(encryptionType)) {
                    tokenCEOEncryptor.encryptStream(inputStream, outputStream, version, secretKey);
                } else {
                    tokenChaosEncryptor.encryptStream(inputStream, outputStream, version, secretKey);
                }
            } else {
                if ("enhanced".equals(encryptionType)) {
                    CEOEncryptor.encryptStream(inputStream, outputStream, version);
                } else {
                    ChaosEncryptor.encryptStream(inputStream, outputStream, version);
                }
            }
        }

        logger.debug("{}加密完成: {} -> {} (原始大小: {}, 加密后大小: {})",
                (secretKey != null ? "Token+" : "") + encryptionType,
                inputFile.getName(), encryptedFile.getName(),
                inputFile.length(), encryptedFile.length());

        return encryptedFile;
    }

    // ============== 辅助方法 ==============

    // 保存MultipartFile到临时文件
    private File saveMultipartFileToTemp(MultipartFile file) throws IOException {
        String extension = extractFileExtension(file.getOriginalFilename());
        String suffix = extension.isEmpty() ? "" : "." + extension;
        File tempFile = File.createTempFile("upload-", suffix);

        try (InputStream inputStream = file.getInputStream();
             FileOutputStream outputStream = new FileOutputStream(tempFile)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        }

        logger.debug("保存临时文件: {} (大小: {} 字节)", tempFile.getAbsolutePath(), tempFile.length());
        return tempFile;
    }

    // 上传文件到OSS（修改为接收路径参数）
    private void uploadToOSS(File encryptedFile, String ossPath) {
        try {
            ossUploader.uploadToOSS(encryptedFile, ossPath);
            logger.info("文件上传OSS成功: {}", ossPath);
        } catch (Exception e) {
            throw new RuntimeException("OSS上传失败", e);
        }
    }

    // 清理临时文件
    private void deleteTempFiles(File... files) {
        for (File file : files) {
            if (file != null && file.exists()) {
                deleteFileWithRetry(file.toPath(), 3);
            }
        }
    }

    // 带重试的文件删除方法
    private void deleteFileWithRetry(Path path, int maxRetries) {
        int attempts = 0;
        while (attempts < maxRetries) {
            try {
                Files.delete(path);
                return;
            } catch (IOException e) {
                attempts++;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        logger.error("无法删除文件: {} (尝试 {} 次后失败)", path, maxRetries);
    }

    private void handleUploadFailure(Long fileId, Exception e) {
        try {
            fileMapper.deleteFile(fileId);
            logger.error("上传失败删除记录: {}", fileId, e);
        } catch (Exception ex) {
            logger.error("删除记录失败: {}", fileId, ex);
        }
    }

    // ============== 文件记录创建方法 ==============
    private FFiles createFileRecord(MultipartFile file, FilesFolder folder,
                                    Version version, Long userId, String md5, String encryptionType) {
        FFiles record = new FFiles();
        record.setUserId(userId);
        record.setFolderId(folder.getFolderId());
        record.setDisplayName(file.getOriginalFilename());
        record.setFileSize(file.getSize());
        record.setMimeType(file.getContentType());
        record.setVersionId(version.getVersionId());
        record.setFileMd5(md5);
        record.setEncryptionType(encryptionType); // 保存加密类型到数据库

        // 设置Token（如果用户有密钥）
        Users user = userMapper.findByUserId(userId);
        if (user != null && user.getSecretKey() != null) {
            record.setFilesToken(user.getSecretKey());
        }

        record.setFileType(extractFileExtension(file.getOriginalFilename()));
        // 注意：这里不再设置OSS路径
        return record;
    }

    private FFiles createFileRecord(String fileName, long fileSize, String mimeType,
                                    FilesFolder folder, Version version, Long userId,
                                    String md5, String encryptionType) {
        FFiles record = new FFiles();
        record.setUserId(userId);
        record.setFolderId(folder.getFolderId());
        record.setDisplayName(fileName);
        record.setFileSize(fileSize);
        record.setMimeType(mimeType);
        record.setVersionId(version.getVersionId());
        record.setFileMd5(md5);
        record.setEncryptionType(encryptionType); // 保存加密类型到数据库

        // 设置Token（如果用户有密钥）
        Users user = userMapper.findByUserId(userId);
        if (user != null && user.getSecretKey() != null) {
            record.setFilesToken(user.getSecretKey());
        }

        record.setFileType(extractFileExtension(fileName));
        // 注意：这里不再设置OSS路径
        return record;
    }

    // 生成OSS存储路径（现在在加密完成后调用）
    private String generateOssPath(Long userId, FilesFolder folder, String fileType) {
        String uniqueName = generateUniqueName(fileType);
        return userId + folder.getFolderPath() + "/" + uniqueName;
    }

    // ============== 工具方法 ==============
    // 提取文件后缀
    private String extractFileExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) return "";
        int dotIndex = fileName.lastIndexOf('.');
        return (dotIndex > 0) ? fileName.substring(dotIndex + 1).toLowerCase() : "";
    }

    // 生成唯一文件名
    private String generateUniqueName(String extension) {
        String uuid = UUID.randomUUID().toString();
        return extension.isEmpty() ? uuid : uuid + "." + extension;
    }

    // 检查文件是否已存在
    @Override
    public FFiles selectFileByMd5(String md5) {
        return fileMapper.selectFileByMd5(md5);
    }

    // 根据文件名确定MIME类型
    private String determineMimeType(String fileName) {
        String extension = extractFileExtension(fileName);
        switch (extension) {
            case "doc":
                return "application/msword";
            case "docx":
                return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            case "xls":
                return "application/vnd.ms-excel";
            case "xlsx":
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            case "pdf":
                return "application/pdf";
            case "txt":
                return "text/plain";
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            default:
                return "application/octet-stream";
        }
    }
}
